#pragma once
#include<iostream>
#include<unistd.h>
#include"Socket.hpp"
#include"Log.hpp"
#include<sys/poll.h>
using namespace SocketModule;
using namespace LogModule;
class PollServer
{
    const static int size=4096;//定义一个数组容量的大小
    const static int defaultfd=-1;//文件描述符的缺省值
public:
    PollServer(int port)//传入端口号构建listen套接字
    : _listensock(std::make_unique<TcpSocket>())//创建一个TCP套接字
    ,_isrunning(false)
    {
        _listensock->BuildTcpSocketMethod(port);//初始化listen套接字

        //想让pollserver管理多个文件描述符

        //通过循环对这个辅助数组进行一个初始化的操作
        for(int i=0;i<size;i++)
        {
            _fds[i].fd=defaultfd;
            _fds[i].events=0;
            _fds[i].revents=0;
        }

        _fds[0].fd=_listensock->Fd();
        _fds[0].events=POLLIN;//只关心POLLIN事件 
    }
    //到这里就有了一个listen套接字了
    void Start()//启动监听
    {
        int timeout=1000;
        _isrunning = true;//运行中，开始监听
        while(_isrunning)
        {
            
            //poll这里我们不需要对参数进行重置相关的操作的，因为输入输出参数是分离的
            
            //第一个参数是文件描述符，第二个参数是数组大小，第三个参数是
            int n=poll(_fds,size,timeout);
            //写、异常、和超时的情况我们都设置为nullptr了

            //对select的返回值进行相关操作
            switch(n)
            {
            case -1 ://返回值是-1的话表示参数传错了
                LOG(LogLevel::ERROR)<<"select error";
                break;
            case 0://返回值是0的话说明是常规的消息，
                LOG(LogLevel::INFO)<<"time out...";
                break;
            default :
            //有事件就绪了,就不仅仅是新链接到来了把？读事件就绪啊？
                LOG(LogLevel::DEBUG)<<"有事件就绪了。。。";
                //新链接到来处理就绪的事件
                Dispatcher();
                
                break;
            }
        }
        _isrunning = false;//运行结束，监听结束
    }
    //处理就绪的事件
    void Dispatcher()
    {
        //进入到这里不仅仅是新链接到来了，也可能是读事件就绪了，所以这里我们是需要进行判断的
        //指定的文件描述符，在rfds中，就证明该fd就绪了

        for(int i=0;i<size;i++)
        {
            if(_fds[i].fd==defaultfd)//说明此时的文件描述符是一个非法的文件描述符
                continue;
            //这里的文件描述符是合法的，但是不一定就绪

            //如果判断一个指定的文件描述符在集合中呢？
            if(_fds[i].revents&POLLIN);//按位与后不为0证明读事件就绪了
            {
                //但是读事件就绪了，不一定是listen套接字就绪了
                if(_fds[i].fd==_listensock->Fd())
                {
                    //_listensockfd新链接到来
                    Accepter();
                }
                else 
                {
                    //普通的读事件就绪
                    Recver(i);//这个i就是这个文件描述符在原始数组中的下标位置
                }
            }
            // else if (_fds[i].revents&POLLOUT)//写事件就绪了
            // {

            // }
        }
    }
    //获取新链接,链接管理器
    void Accepter()
    {

        //获取新链接
        InetAddr client;//定义客户端信息
        int sockfd=_listensock->Accept(&client);//这里的accrpt是绝对不会阻塞的
        //获得对应的链接，这里的accept不会阻塞的

        //这里是不会阻塞的，因为上层的listensock已经就绪了，将等的过程和拷的过程分离了
        if(sockfd>=0)
        {
            //获取新链接成功到来,我们能否直接read/recv()
            //fd是否读继续，我们是不清楚的
            //只有谁最清楚，未来sockfd上是否有事件就绪----select
            //将新的sockefd，托管给select
            //如何托管？将新的fd放入到辅助数组中即可
            LOG(LogLevel::INFO)<<"get a new link ,sockfd:"<<sockfd<<"client is"<<client.StringAddr();
            int pos=0;
            for( ;pos<size;pos++)
            {
                if(_fds[pos].fd==defaultfd)//这个位置等于缺省，说明这个位置是没有人占用的
                    break;
            }
            //遍历完了数组都没找到合法的fd
            if(pos==size)
            {
                LOG(LogLevel::WARNING)<<"select server full";//服务器被打满了
                close(sockfd);
            }
            else //找到了合法的位置
            {
                _fds[pos].fd =sockfd;
                _fds[pos].events=POLLIN;
                _fds[pos].revents=0;
            }
        }
    }
    //IO处理器
    void Recver(int pos)
    {
        char buffer[1024];
        //我在这里读取的时候，会不会阻塞
        ssize_t n=recv(_fds[pos].fd,buffer,sizeof(buffer)-1,0);//从fd中读取，放到buffer中，最后一个参数设置为0，默认阻塞等待
        if(n>0)//读取成功了
        {
            buffer[n]=0;
            cout<<"client say@"<<buffer<<endl;
        }
        else if(n==0)//代表客户端就退出了
        {
            LOG(LogLevel::INFO)<<"client quit...";
            //不要让select再关心这个文件描述符fd了
            _fds[pos].fd=defaultfd;
            _fds[pos].events=0;
            _fds[pos].revents=0;


            //客户端退出了，那么我们应该将文件描述符关闭
            close(_fds[pos].fd);
        }
        else //读出错了
        {
            LOG(LogLevel::ERROR)<<"recv error";
            //不要让select再关心这个文件描述符fd了
            _fds[pos].fd=defaultfd;
            _fds[pos].events=0;
            _fds[pos].revents=0;


            //客户端退出了，那么我们应该将文件描述符关闭
            close(_fds[pos].fd);
        }

    }
    void PrintFd()
    {
        cout<<"_fds[]:";
        for(int i=0;i<size;i++)
        {
            cout<<_fds[i].fd<<" ";

        }
        cout<<"\r\n";
    }
    void Stop()//停止监听
    {
        _isrunning = false;//停止监听
    }
    ~PollServer()
    {

    }
private:
    std::unique_ptr<Socket>_listensock;//创建一个基类套接字
    bool _isrunning = false;

    struct pollfd _fds[size];//使用固定大小的写法
    //struct pollfd *_fds;//也可以使用指针
};